﻿using System.Collections;
using Microsoft.Extensions.Caching.Memory;

namespace BaseRepository.Cache;

public class CacheRepository : ICacheRepository
{
    private readonly IMemoryCache _memoryCache;

    public CacheRepository(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    /// <summary>
    /// 校验参数是否合法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <exception cref="InvalidOperationException"></exception>
    private void ValidateValueType<T>()
    {
        //因为IEnumerable、IQueryable等有延迟执行的问题，造成麻烦，因此禁止用这些类型
        var type = typeof(T);
        // 判断是不是 泛型的类型 比如 ienumerable<string>  要把string 去掉再比较
        if (type.IsGenericType)
        {
            type = type.GetGenericTypeDefinition();
        }

        if (type == typeof(IEnumerable<>) || type == typeof(IQueryable<>) || type == typeof(IEnumerable) || type == typeof(IQueryable) || type == typeof(IAsyncEnumerable<>))
        {
            throw new InvalidOperationException($"TResult of {type} is not allowed, please use List<T> or T[] instead.");
        }
    }
    /// <summary>
    /// 避免缓存雪奔  这边才是随机时间
    /// </summary>
    /// <param name="entry"></param>
    /// <param name="baseExpireSeconds"></param>
    private void InitCacheEntry(ICacheEntry entry, int baseExpireSeconds)
    {
        // 如果是0 那么就不设置缓存过期时间
        if (baseExpireSeconds == 0)
        {
            return;
        }
        // 获取随机数  数字娶设定值和两倍中间的一个值
        var time = GetRandomTime(baseExpireSeconds);
        // 设置相对过期时间
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(time);
    }
    /// <summary>
    /// 获取随机数
    /// </summary>
    /// <param name="seconds"></param>
    /// <returns></returns>
    private int GetRandomTime(int seconds)
    {
        return Random.Shared.Next(seconds, seconds * 2);
    }
    /// <summary>
    /// 从缓存中获取  或者创建缓存
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="valueFactory"></param>
    /// <param name="baseExpireSeconds"></param>
    /// <returns></returns>
    public async Task<T?> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T?>> valueFactory, int baseExpireSeconds = 5 * 60)
    {
        ValidateValueType<T>();
        //因为IMemoryCache保存的是一个CacheEntry，所以null值也认为是合法的，因此返回null不会有“缓存穿透”的问题
        //不调用系统内置的CacheExtensions.GetOrCreate，而是直接用GetOrCreate的代码，这样免得包装一次委托
        // 如果不存在  那就创建一个entry
        if (!_memoryCache.TryGetValue(key, out T? result))
        {
            // 创建缓存 对象  dispose entity的时候才会存入缓存
            using var entity = _memoryCache.CreateEntry(key);
            InitCacheEntry(entity, baseExpireSeconds);
            result = await valueFactory(entity);
            entity.Value = result;
        }

        return result;
    }

    /// <summary>
    /// 从缓存中获取  或者创建缓存
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="valueFactory"></param>
    /// <param name="baseExpireSeconds"></param>
    /// <returns></returns>
    public T? GetOrCreate<T>(string key, Func<ICacheEntry, T?> valueFactory, int baseExpireSeconds = 5 * 60)
    {
        ValidateValueType<T>();
        if (!_memoryCache.TryGetValue(key, out T? result))
        {
            // 创建缓存 对象  dispose entity的时候才会存入缓存
            using var entity = _memoryCache.CreateEntry(key);
            InitCacheEntry(entity, baseExpireSeconds);
            result = valueFactory(entity);
            entity.Value = result;
        }
        return result;
    }
    /// <summary>
    /// 获取缓存
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <returns></returns>
    public T? Get<T>(string key)
    {
        return _memoryCache.Get<T>(key);
    }
    /// <summary>
    /// 创建缓存
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <param name="baseExpireSeconds"></param>
    public void Set<T>(string key, T value, int baseExpireSeconds = 5 * 60)
    {
        ValidateValueType<T>();
        if (baseExpireSeconds == 0)
        {
            _memoryCache.Set(key, value);
            return;
        }

        var expireTime = GetRandomTime(baseExpireSeconds);
        _memoryCache.Set(key, value, TimeSpan.FromSeconds(expireTime));
    }

    /// <summary>
    /// 删除缓存
    /// </summary>
    /// <param name="key"></param>
    public void Remove(string key)
    {
        _memoryCache.Remove(key);
    }
    /// <summary>
    /// 删除多个缓存
    /// </summary>
    /// <param name="keys"></param>
    public void Remove(List<string> keys)
    {
        foreach (var key in keys)
        {
            _memoryCache.Remove(key);
        }
    }
    /// <summary>
    /// 是否存在这个key
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public bool TryGetValue(string key, out object? value)
    {
        return _memoryCache.TryGetValue(key, out value);
    }
}